Within this section, you'll find best practices and resources for developing games for the Gameboard.

If you haven't already, checkout the Gameboard Overview that will give a quick introduction to Gameboard and it's mission. There you will also find links to our support portal and discord.

Development Examples

Example project

This is a Unity example project and a Godot example project showing the various controllers and utilites setup within a project. There are tag corresponding to the various versions as we try to update the examples regularly as we update the SDK.

You can either clone the repository to open in Unity, or download the Unity Package from the UnityPackage folder and pull that into an existing project.

Base Concepts Example Documentation

Companion Example Documentation

Tools

Quick-start Prefabs / Scripts Documentation [Unity]

Debugging Utilities Documentation

General

Unity

Advanced Systems Documentation

When your game is launched on Gameboard, there are guidelines you should follow to make sure users don't become disconnected from the experience. This will happen when the user is looking at a blank screen, or even a loading screen without any animation. It's even more vital for games with long load times.

In Unity, you should start your game with the smallest scene size possible. This way, Gameboard will display this scene in just a few seconds. Try to not include any media assets in this scene - except for loading bar assets (if you're going to use a loading bar in this scene).

It's suggested (but not required) to keep your loading bar scene separate, and load directly into that scene after your initial small scene has loaded. This way you can easily manage the loading bar scene, reusing it as needed. We offer example source to guide you to the best way to load your game/scenes.

Here is the Unity project folder for our Loading Example, which will give you all of the tools you need to load your game properly on Gameboard. In the next steps, we'll go over this in detail:

Step 1: The First Scene

Begin by opening the "FirstScene" in Assets/Scenes. This is a good place to launch your game from. In this scene, you'll see a LoadingManager GameObject, which is a Prefab located in the Assets/ folder. You should drop this Prefab into the launch scene of your project. This is a singleton, so it will stay loaded no matter where you are in your game.

Step-by-Step

Click on the LoadingManager GameObject in your scene hierarchy. Notice the script (LoadingManager.cs in the Assets/Scripts/ folder) in the Inspector. It has an exposed variable for InitialSceneToLoad. in this case, we have TitleScreen as our first scene. When our game starts, and the LoadingManager loads - it will first take us to the TitleScreen scene. Future scene loads will be set by you via script, but this initial exposed variable helps if you don't want to write any more code for your initial launch scene.

Now, hit Unity's Play Button to see the LoadingManager in action. The sequence will immediately load the LoadingScene.

Step-by-Step

Step 2: The Loading Scene

Once LoadingScene has loaded, the Loading.cs script (Attached to the LoadingSystem GameObject) will immediately begin loading the scene you designated in the LoadingManager. If you need to load a scene manually (without using the exposed variable), you can do so from this function:

LoadingManager.LoadScene("SceneName");

Since this is a static function, you can call this from any script and it will immediately load LoadingScene, and then asynchronously begin loading the scene you designated.

In LoadingScene, the LoadingSystem GameObject has a script attached which handles the loading of your scene. This is also where you'll define your loading bar image. We are using fillAmount from an Image component to show the bar's progress, but you can change it to anything you like.

Step 3: Your Scene Loads

Once the loading progress reaches 1.0, your scene will instantly activate. However, based on the design of your loaded scene, you could see a long pause between the bar filling and your scene appearing. This is usually caused by the number of GameObjects and set up scripts that are in your loaded scene.

When possible, try to bring in your assets in a staggered manner. For example, let's say you go to the title screen scene, then a lobby scene, then finally the game scene. Don't wait until you're at the game scene to load all of your game assets - try doing it in earlier scenes so you space them out. Place them in singleton GameObject then you can keep them loaded, which will speed up subsequent game load times.

This guide will go over implementing a Lobby system using Unity's Lobby and Relay services. These services are free for up to 50 concurrent users and will cost 16 cents per addition ccu (Concurrent user). You can find more information here: UGS Pricing.

Installing packages and setting up the project for lobbies

Authentication

Now that the necessary packages are installed create a scene that will handle authenticating an anonymous user.

if (!AuthenticationService.Instance.IsSignedIn)
{
      await AuthenticationService.Instance.SignInAnonymouslyAsync();
}

Creating a Lobby

A client will have the option to create a private or public lobby. Creating a lobby will require certain parameters as well as offer a few optional ones: Lobby name, Lobby visibility, Lobby size, as well as host information. To create a public lobby you'll need:

string lobbyName = "name";
int maxPlayers;
var options = new CreateLobbyOptions();
options.IsPrivate = false;
Lobby lobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, options);

Lobbies will be delisted if the client has not sent a message within 30 seconds. To counteract this you will need to send a heartbeat request. The amount of times you can send this request is 5 every thirty seconds. Setting up a coroutine or an async to containing:

await Lobbies.Instance.SendHeartbeatPingAsync(lobbyID);

Getting and joining public lobbies

Once authenticated a client can retrieve the listed public lobbies with:

try
{
    var lobbyIds = await LobbyService.Instance.GetJoinedLobbiesAsync();
}
catch (LobbyServiceException e)
{
    Debug.Log(e);
}

If a lobby is retrieved you can join the lobby with a specific Lobby id:

try
{
    await LobbyService.Instance.JoinLobbyByIdAsync("lobbyId");
}
catch (LobbyServiceException e)
{
    Debug.Log(e);
}

Grabbing gameboard usernames with the unity SDK and using them for lobbies

Setup for the Unity sdk can be found here: Unity SDK Overview. Once the Unity sdk is setup you can retrieve the logged in usernames through Player presence observers. These player presence updates will contain a userName in the event args.

public void UpdateUserPresence(GameboardUserPresenceEventArgs eventArgs)
    {
        if (eventArgs.userName != userName)
        {
            userName = eventArgs.userName;
        }
    }

This username is the local username of the client logged in. This name will be sent to the host and then to every other client to display the name locally. In order to make a script a network behavior you'll need to derive from NetworkBehaviour instead of MonoBehaviour. From there overriding the OnNetworkSpawn function will allow for a call when the client connects to the server. (Clients can also be servers so this function will also be called when connecting as a host). From this point grab the clients username and any other information you may want to send and store that information in a serializable struct. Example:

PlayerInfoStruct pis;
private Dictionary<ulong, PlayerInfoStruct> playersInLobby = new Dictionary<ulong, PlayerInfoStruct>();

// Called when connecting as a host (server) or as just a player (client)
public override void OnNetworkSpawn()
    {
        if (IsServer)
        {
            NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnectedCallback;
            UpdateInterface();
        }

        pis = new PlayerInfoStruct();
        pis.readied = false;
        pis.playerName = PlayerPresenceManager.presenceManager.activePlayerList[0].userName;
        playersInLobby.Add(NetworkManager.Singleton.LocalClientId, pis);

        if (!IsServer)
        {
            foreach (var player in playersInLobby) UpdatePlayerServerRpc(player.Key, JsonUtility.ToJson(player.Value)); // Send Player data from client to server
        }
        // Client uses this in case host destroys the lobby
        NetworkManager.Singleton.OnClientDisconnectCallback += OnClientDisconnectCallback;

        OnReadyClicked();
    }

    [ServerRpc] // Client send info to server
    private void UpdatePlayerServerRpc(ulong clientId, string sentPis)
    {
        PlayerInfoStruct myObject = JsonUtility.FromJson<PlayerInfoStruct>(sentPis);

        // Check if we have the player yet, if not add them to the lobby
        if (!playersInLobby.ContainsKey(clientId)) playersInLobby.Add(clientId, myObject);

        else
        {
            PlayerInfoStruct pisGet = playersInLobby[clientId];
            pisGet.readied = true;
        }

        // Now invoke on all the clients to have updated player data
        foreach (var player in playersInLobby) UpdatePlayerClientRpc(player.Key, JsonUtility.ToJson(player.Value));
    }

    [ClientRpc] // Server invokes on all clients
    private void UpdatePlayerClientRpc(ulong clientId, string sentPis)
    {
        PlayerInfoStruct myObject = JsonUtility.FromJson<PlayerInfoStruct>(sentPis);
        if (IsServer) return;

        // Check if we have the player yet, if not add them to the lobby
        if (!playersInLobby.ContainsKey(clientId)) playersInLobby.Add(clientId, myObject);

        else
        {
            PlayerInfoStruct pisGet = playersInLobby[clientId];
            pisGet.readied = true;
        }
        UpdateInterface(); // Method to update your visual lobby based on the now updated playersInLobby
    }

This snippet will send all of the necessary data to all clients within the lobby. It does this by storing data in a serializable struct that is then sent to a server rpc to send to all clients. This struct is converted back into a functional struct using JSON.